﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Net;
using System.Threading;

namespace Innovox.NetTools
{
    class HttpRequestContext
    {
        const int DefaultTimeout = 20500;

        ManualResetEvent AllDone;
        StringBuilder requestData;
        HttpWebResponse response;
        public string ErrorMessage { get; private set; }

        public HttpWebRequest Request { get; private set; }

        public void Initialize()
        {
            requestData = new StringBuilder("");
            AllDone = new ManualResetEvent(false);
            ErrorMessage = null;
        }

        static public HttpRequestContext Create(string url)
        {
            HttpWebRequest request = HttpWebRequestBuilder.CreateRequest(url);
            HttpRequestContext context = new HttpRequestContext();
            context.Request = request;
            context.Initialize();
            return context;
        }

        public void GetResponse()
        {
            IAsyncResult asyncResult = (IAsyncResult)Request.BeginGetResponse(new AsyncCallback(ResponseReceivedCallBack), this);
            // this line implements the timeout, if there is a timeout, the callback fires and the request becomes aborted
            ThreadPool.RegisterWaitForSingleObject(asyncResult.AsyncWaitHandle, TimeoutCallback, this, DefaultTimeout, true);
            // The response came in the allowed time. The work processing will happen in the 
            // callback function.
            AllDone.WaitOne();
            if (response != null)
                response.Close();
            
            if( ErrorMessage != null )
                throw new Exception(ErrorMessage);
        }

        private static void ResponseReceivedCallBack(IAsyncResult asyncResult)
        {
            // State of request is asynchronous.
            HttpRequestContext context = (HttpRequestContext)asyncResult.AsyncState;
            try
            {
                context.GetHttpWebResponse(asyncResult);
                // Read the response into a Stream object.
                HttpRequestStreamReader reader = new HttpRequestStreamReader(context);
                reader.BeginRead();
                return;
            }
            catch (Exception e)
            {
                context.AbortRequest();
                context.SetErrorMessage(e.Message);
            }
            context.SetAllDone();
        }

        private static void TimeoutCallback(object state, bool timedOut)
        {
            if (timedOut)
            {
                HttpRequestContext context = state as HttpRequestContext;
                if (context != null)
                    context.AbortRequest();
            }
        }
        
        private void GetHttpWebResponse(IAsyncResult asyncResult)
        {
            response = (HttpWebResponse)Request.EndGetResponse(asyncResult);
        }

        public void AbortRequest()
        {
            Request.Abort();
        }

        public void SetAllDone()
        {
            AllDone.Set();
        }

        public string RequestDataToString()
        {
            return requestData.ToString();
        }

        public void SetMethod(string method)
        {
            Request.Method = method;
        }

        public Stream GetResponseStream()
        {
           return  response.GetResponseStream();
        }

        public void AppendBytesToRequestData(byte[] responseData, int numBytes)
        {
            string utf8Response = Encoding.UTF8.GetString(responseData, 0, numBytes);
            requestData.Append(utf8Response);
        }

        public void SetErrorMessage(string error)
        {
            ErrorMessage = ErrorHeader() + error;
        }

        private string ErrorHeader()
        {
            return Request.Address + ", ";
        }
    }
}
